package com.sq.util.encrypt;

import java.io.File;
import java.io.FileInputStream;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sq.util.ByteUtil;
import com.sq.util.IOUtil;
import com.sq.util.StringUtil;
import com.sq.util.exception.CommonException;

/**
 * 签名工具类
 */
public class SignUtil {
    private static Logger log = LoggerFactory.getLogger(SignUtil.class);

    public static final String MD5 = "MD5";
    public static final String SHA1 = "SHA-1";
    public static final String SHA256 = "SHA-256";
    public static final String RSA = "RSA";
    public static final String EC = "EC";
    private static final String SHA1_WITH_RSA = "SHA1withRSA";
    private static final String MD5_WITH_RSA = "MD5withRSA";
    private static final String SHA1_WITH_ECDSA = "SHA1withECDSA";
    private static final String HMACSHA1 = "HmacSHA1";

    public static String md5(File file) {
        try (FileInputStream in = new FileInputStream(file)) {
            byte[] arr = new byte[10240];
            int len = 0;
            MessageDigest messagedigest = java.security.MessageDigest.getInstance("MD5");
            while ((len = in.read(arr)) > 0) {
                messagedigest.update(arr, 0, len);
            }
            return ByteUtil.toHexString(messagedigest.digest());
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new CommonException("算签名出错:" + e.getMessage());
        }
    }

    public static String md5(byte[] data) {
        return encode(data, MD5);
    }

    public static String sha1(byte[] data) {
        return encode(data, SHA1);
    }

    public static String sha256(byte[] data) {
        return encode(data, SHA256);
    }

    /**
     * 产生签名
     */
    public static byte[] signSha1WithEcdsa(byte[] data, String hexPrivateKey) {
        return sign(data, hexPrivateKey, SHA1_WITH_ECDSA, EC);
    }

    /**
     * 验证签名
     */
    public static boolean verifySha1WithEcdsa(byte[] data, byte[] sign, String hexPublicKey) {
        return verify(data, sign, hexPublicKey, SHA1_WITH_ECDSA, EC);
    }

    /**
     * 产生签名
     */
    public static byte[] signMd5WithRsa(byte[] data, String hexPrivateKey) {
        return sign(data, hexPrivateKey, MD5_WITH_RSA, RSA);
    }

    /**
     * 验证签名
     */
    public static boolean verifyMd5WithRsa(byte[] data, byte[] sign, String hexPublicKey) {
        return verify(data, sign, hexPublicKey, MD5_WITH_RSA, RSA);
    }

    /**
     * 产生签名
     */
    public static byte[] signSha1WithRsa(byte[] data, String hexPrivateKey) {
        return sign(data, hexPrivateKey, SHA1_WITH_RSA, RSA);
    }

    /**
     * 验证签名
     */
    public static boolean verifySha1WithRsa(byte[] data, byte[] sign, String hexPublicKey) {
        return verify(data, sign, hexPublicKey, SHA1_WITH_RSA, RSA);
    }

    /**
     * 产生签名
     */
    public static byte[] hmacSha1(byte[] data, byte[] key) throws InvalidKeyException, NoSuchAlgorithmException {
        SecretKeySpec signingKey = new SecretKeySpec(key, HMACSHA1);
        Mac mac = Mac.getInstance(HMACSHA1);
        mac.init(signingKey);
        return mac.doFinal(data);
    }

    public static Key genRsaKey() {
        return genKey(RSA, 512);
    }

    public static Key genEcKey() {
        return genKey(EC, 256);
    }

    public static Key genKey(String algorithm, int keysize) {
        try {
            KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(algorithm);
            keyPairGen.initialize(keysize);
            KeyPair keyPair = keyPairGen.generateKeyPair();
            Key key = new Key();
            key.setHexPublicKey(ByteUtil.toHexString(keyPair.getPublic().getEncoded()));
            key.setHexPrivateKey(ByteUtil.toHexString(keyPair.getPrivate().getEncoded()));
            return key;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new CommonException("生成密钥对出错:" + e.getMessage());
        }
    }

    private static byte[] sign(byte[] data, String hexPrivateKey, String signAlgorithm, String keyAlgorithm) {
        try {
            Signature sigEng = Signature.getInstance(signAlgorithm);
            byte[] pribyte = ByteUtil.toBytes(StringUtil.trim(hexPrivateKey));
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pribyte);
            KeyFactory fac = KeyFactory.getInstance(keyAlgorithm);
            sigEng.initSign(fac.generatePrivate(keySpec));
            sigEng.update(data);
            return sigEng.sign();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new CommonException("生成签名出错:" + e.getMessage());
        }

    }

    private static boolean verify(byte[] data, byte[] sign, String hexPublicKey, String signAlgorithm,
            String keyAlgorithm) {
        try {
            Signature sigEng = Signature.getInstance(signAlgorithm);
            byte[] pubbyte = ByteUtil.toBytes(StringUtil.trim(hexPublicKey));
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pubbyte);
            KeyFactory fac = KeyFactory.getInstance(keyAlgorithm);
            sigEng.initVerify(fac.generatePublic(keySpec));
            sigEng.update(data);
            return sigEng.verify(sign);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new CommonException("验证签名出错:" + e.getMessage());
        }
    }

    private static String encode(byte[] data, String type) {
        try {
            MessageDigest alga = java.security.MessageDigest.getInstance(type);
            alga.update(data);
            byte[] digesta = alga.digest();
            return ByteUtil.toHexString(digesta);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new CommonException("生成签名出错:" + e.getMessage());
        }
    }

}
